
'use strict';
const isGeneratorFunction = require('is-generator-function');
const debug = require('debug')('koa:application');
const onFinished = require('on-finished');
const statuses = require('statuses');
const Emitter = require('events');
const util = require('util');
const Stream = require('stream');
const only = require('only');
const convert = require('koa-convert');
const deprecate = require('depd')('koa');
const { HttpError } = require('http-errors');


//http:node提供的系统包
const http = require('http');
//koa-compose:生成尾递归函数的第三方包
const compose = require('koa-compose');
//./response ./request ./context : 路径
const response = require('./response');
const request = require('./request');
const context = require('./context');
module.exports = class Application extends Emitter {
    //constructor在Application被实例化自动调用!!!
    constructor(options) {
        super();
        options = options || {};
        this.proxy = options.proxy || false;
        this.subdomainOffset = options.subdomainOffset || 2;
        this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
        this.maxIpsCount = options.maxIpsCount || 0;
        this.env = options.env || process.env.NODE_ENV || 'development';
        if (options.keys) this.keys = options.keys;
        // util.inspect.custom support for node 6+
        /* istanbul ignore else */
        if (util.inspect.custom) {
          this[util.inspect.custom] = this.inspect;
        }



        //核心代码  this:app
        this.middleware = [];
        this.context = Object.create(context);
        this.request = Object.create(request);
        this.response = Object.create(response);
    }
    /*use的作用就是往middleware中添加中间件函数*/
    use(fn) {
      if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
      if (isGeneratorFunction(fn)) {
          deprecate('Support for generators will be removed in v3. ' +
              'See the documentation for examples of how to convert old middleware ' +
              'https://github.com/koajs/koa/blob/master/docs/migration.md');
          fn = convert(fn);
      }
      debug('use %s', fn._name || fn.name || '-');


      this.middleware.push(fn);
      return this;
    }
    /*
      启动http服务的!!
      ...运用在形参中代表组装数组
    */
    listen(...args) {
        debug('listen');
        //创建服务   this.callback返回的函数才是真正的请求对应的异步回调
        const server = http.createServer(this.callback());
        //启动服务 ...运用在实参中代表平铺数组
        return server.listen(...args);
    }

    /*
      callback返回的函数才是真正的请求对应的异步回调
      启动服务时 callback已经被调用
    */
    callback() {
        //this.middleware 中间件函数
        // fn:compose返回的函数,尾递归函数;
        const fn = compose(this.middleware);

        if (!this.listenerCount('error')) this.on('error', this.onerror);

        //请求对应的异步回调
        const handleRequest = (req, res) => {
            /*
              req:node提供的请求信息
              res:node提供的响应信息
              ctx:koa上下文
            */
            const ctx = this.createContext(req, res);
            return this.handleRequest(ctx, fn);
        };

        return handleRequest;
    }
    /*
      创建koa上下文;每一次请求都会生成一个新的ctx
    */
    createContext(req, res) {
        /*
            req:node提供的请求信息
            res:node提供的响应信息

            构建ctx关系图(ctx-2.png)
        */
        const context = Object.create(this.context);
        const request = context.request = Object.create(this.request);
        const response = context.response = Object.create(this.response);

        /*
        context
        context.request
        context.response 都具备app req res属性
        */
        context.app = request.app = response.app = this;
        context.req = request.req = response.req = req;
        context.res = request.res = response.res = res;

        // context context.request context.response 之间可以进行相互访问
        request.ctx = response.ctx = context;
        request.response = response;
        response.request = request;

        //context额外多的信息
        context.originalUrl = request.originalUrl = req.url;
        context.state = {};
        return context;
    }
    /*
      ctx:koa上下文
      fnMiddleware:尾递归函数
    */
    handleRequest(ctx, fnMiddleware) {
        const res = ctx.res;
        res.statusCode = 404;
        const onerror = err => ctx.onerror(err);
        const handleResponse = () => respond(ctx);
        onFinished(res, onerror);
        //将ctx交给尾递归函数 并 执行尾递归函数
        return fnMiddleware(ctx).then(handleResponse).catch(onerror);
    }





  toJSON() {
    return only(this, [
      'subdomainOffset',
      'proxy',
      'env'
    ]);
  }
  inspect() {
    return this.toJSON();
  }
  onerror(err) {
    // When dealing with cross-globals a normal `instanceof` check doesn't work properly.
    // See https://github.com/koajs/koa/issues/1466
    // We can probably remove it once jest fixes https://github.com/facebook/jest/issues/2549.
    const isNativeError =
      Object.prototype.toString.call(err) === '[object Error]' ||
      err instanceof Error;
    if (!isNativeError) throw new TypeError(util.format('non-error thrown: %j', err));

    if (404 === err.status || err.expose) return;
    if (this.silent) return;

    const msg = err.stack || err.toString();
    console.error(`\n${msg.replace(/^/gm, '  ')}\n`);
  }
  static get default() {
    return Application;
  }
};

function respond(ctx) {
  // allow bypassing koa
  if (false === ctx.respond) return;

  if (!ctx.writable) return;

  const res = ctx.res;
  let body = ctx.body;
  const code = ctx.status;

  // ignore body
  if (statuses.empty[code]) {
    // strip headers
    ctx.body = null;
    return res.end();
  }

  if ('HEAD' === ctx.method) {
    if (!res.headersSent && !ctx.response.has('Content-Length')) {
      const { length } = ctx.response;
      if (Number.isInteger(length)) ctx.length = length;
    }
    return res.end();
  }

  // status body
  if (null == body) {
    if (ctx.response._explicitNullBody) {
      ctx.response.remove('Content-Type');
      ctx.response.remove('Transfer-Encoding');
      return res.end();
    }
    if (ctx.req.httpVersionMajor >= 2) {
      body = String(code);
    } else {
      body = ctx.message || String(code);
    }
    if (!res.headersSent) {
      ctx.type = 'text';
      ctx.length = Buffer.byteLength(body);
    }
    return res.end(body);
  }

  // responses
  if (Buffer.isBuffer(body)) return res.end(body);
  if ('string' === typeof body) return res.end(body);
  if (body instanceof Stream) return body.pipe(res);

  // body: json
  body = JSON.stringify(body);
  if (!res.headersSent) {
    ctx.length = Buffer.byteLength(body);
  }
  res.end(body);
}

module.exports.HttpError = HttpError;
